package com.yss.netty.codec;

import com.yss.exception.RouterParallelException;
import com.yss.netty.protocol.CommandType;
import com.yss.netty.protocol.Message;
import com.yss.netty.protocol.SerType;
import com.yss.netty.util.Constants;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;

import java.util.List;

/**
 * Decoder
 * MultiThread safe
 */
public class NettyDecode extends ReplayingDecoder<NettyDecode.State> {

    private int len = 0;
    private long opaque = 0L;
    private int serTypeIndex = 0;
    private int commandTypeIndex = 0;

    public NettyDecode() {
        super(State.MAGIC);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        switch (state()) {
            case MAGIC: {
                checkMagic(in.readByte());
                checkpoint(State.VERSION);
                break;
            }
            case VERSION: {
                checkVersion(in.readByte());
                checkpoint(State.OPAQUE);
                break;
            }
            case OPAQUE: {
                opaque = in.readLong();
                checkpoint(State.SERTYPE);
                break;
            }
            case SERTYPE: {
                serTypeIndex = in.readByte();
                checkpoint(State.TYPE);
                break;
            }
            case TYPE: {
                commandTypeIndex = in.readByte();
                checkpoint(State.LENGTH);
                break;
            }
            case LENGTH: {
                len = in.readInt();
                checkpoint(State.BODY);
                break;
            }
            case BODY: {
                byte[] body = new byte[len];
                in.readBytes(body);

                SerType serType = SerType.values()[serTypeIndex];
                CommandType commandType = CommandType.values()[commandTypeIndex];

                out.add(Message.of(commandType, serType, body, opaque));
                checkpoint(State.MAGIC);
                break;
            }
        }
    }

    private void checkMagic(byte messageMagic) {
        if (Constants.MAGIC != messageMagic) {
            throw new RouterParallelException("illegal packet [magic] " + messageMagic);
        }
    }

    private void checkVersion(byte messageVersion) {
        if (Constants.VERSION != messageVersion) {
            throw new RouterParallelException("illegal packet [version] " + messageVersion);
        }
    }

    @SuppressWarnings("all")
    enum State {
        MAGIC,
        VERSION,
        OPAQUE,
        SERTYPE,
        TYPE,
        LENGTH,
        BODY
    }

}
